package org.pulloid;

import java.io.IOException;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;



public class ElementContext<T> extends NodeContext<T> {
	

	int depth;
	CursorDef<T> cursorDef;
	
	private ElementContext<T> lastChild;
	private ElementContext<T> firstChild;
	private AttributeContext<T> attrChain;

	

	public ElementContext(ElementContext<T> parent, String name) {
		super(parent, name);
	}
	
	public ElementContext<T> selectElement(String name) {
		ElementContext<T> ctx = new ElementContext<T>(this, name);
		ctx.depth = this.depth + 1;
		if(firstChild == null)
			firstChild = ctx;
		else
			lastChild.nextSibling = ctx;
		lastChild = ctx;
		return ctx;
	}

	public ElementContext<T> createFieldOnAttribute(String fieldName, String attributeName) {
		AttributeContext<T> ctx = new AttributeContext<T>(this, attributeName);
		ctx.registerField(fieldName);
		if(attrChain != null) // We don't care about the order for attributes, because we can get all of them while the parser is on the start tag
			ctx.nextSibling = attrChain;
		attrChain = ctx;
		return this;
	}
	
	public ElementContext<T> createField(String name) {
		registerField(name);
		return this;
	}
	
	public ElementContext<T> createField(String name, Class<?> type) {
		registerField(name, type);
		return this;
	}
	
	public CursorDef<T> defineCursor(Class<T> cursorType) {
		cursorDef = new CursorDef<T>(cursorType, this);
		return cursorDef;
	}
	
	@Override
	CursorDef<T> findCursorDef() {
		if(this.cursorDef != null)
			return this.cursorDef;
		return super.findCursorDef();
	}
	

	
	/**
	 * Move parser cursor to the next occurrence of this element.
	 * If there is no such occurrence, will try to position to the next sibling, and so on.
	 * @param p
	 * @return
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	ElementContext<T> position(XmlPullParser p) throws XmlPullParserException, IOException {
		/*
		 * This context is the cursor, move forward until we encounter the next cursor tag
		 * There's no need to look for next sibling match
		 */
		if(cursorDef != null) {
			try {
				while(this.depth != p.getDepth() || !name.equals(p.getName()) || !ParserUtil.isStartTag(p)) {
					if(ParserUtil.isEndDocument(p))
						return null;
					p.next();
				}
			} catch(XmlPullParserException e) {
				return null;
			}
			return this;
		}
		
		
		
		/*
		 * Starting from here is the non-cursor case: some elements may be missing, other elements may be ignored
		 */
		
		// Go to the right depth
		try {
			while(this.depth != p.getDepth() || ParserUtil.isEndTag(p)) {
				p.nextTag();
				if(p.getName().equals(parent.name))
					return null;
			}
		} catch(XmlPullParserException e) {
			return null;
		}
		
		// Try to match self, or siblings
		do {
			ElementContext<T> pos = matchSelfOrSibling(p);
			if(pos != null)
				return pos;
		} while(ParserUtil.moveToNextSibling(p)); // Skip sibling
		
		return null;
	}


	/**
	 * Figures out if one of the next siblings of this element might
	 * match the parser current cursor position.
	 * @param p
	 * @return
	 * @throws XmlPullParserException 
	 */
	ElementContext<T> matchSelfOrSibling(XmlPullParser p) throws XmlPullParserException {
		if(p.getName().equals(name) && ParserUtil.isStartTag(p)) {
			return this;
		}
		if(nextSibling != null)
			return ((ElementContext<T>)nextSibling).matchSelfOrSibling(p);
		return null;
	}
	

	/**
	 * Moves the parser cursor to the next occurrence of this element
	 * and fills the current record. If this element has children and/or siblings,
	 * then calls the same method on those elements. Does the same for attributes if there
	 * are any.
	 * @see #position(Puller)
	 */
	@Override
	void hydrate(XmlPullParser p, Reflector<T> record) throws XmlPullParserException, IOException {
		ElementContext<T> pos = null;
		if(cursorDef == null) { // If we are in the element that created the cursor there's no need to position() as it's already been done from the cursor
			pos = position(p);
			if(pos == null)
				return;
		} else {
			pos = this;
		}
		
		if(pos == this) {
			if(attrChain != null)
				attrChain.hydrate(p, record); // Do all attributes, no need to position, we're at the right position already
			if(firstChild != null) {
				firstChild.hydrate(p, record); // Send to children
			} else if(setter != null) {
				record.set(setter, p.nextText(), transformer); // Move to my end tag
			}
			if(cursorDef == null && nextSibling != null) {
				nextSibling.hydrate(p, record);
			}
		} else {
			pos.hydrate(p, record);
		}
	}


}
